/*
 * Copyright (C) 2020 The LINN Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.juqimiao.linn.extension;

import com.juqimiao.linn.context.BusinessPoint;
import com.juqimiao.linn.context.BusinessPointContext;
import com.juqimiao.linn.context.ContextManager;
import com.juqimiao.linn.context.ContextObject;
import com.juqimiao.linn.exception.BasicError;
import com.juqimiao.linn.exception.FrameworkException;
import com.juqimiao.linn.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Todo: Add logging logic.
 *
 * 扩展执行器。
 *
 * <p>
 *     它负责按照一下规则查找一个Extenstion，并执行给定的方法。
 *     定位规则：
 *     假设
 *     targetClz 为 CustomerValidExtensionPt Interface.
 *     bizScenarioUniqueIdentity 为 "bizid&usercase&scenario"
 *    1) 那么搜索先通过CustomerValidExtensionPt获取所有实现这个接口的bean.
 *    2) 首先匹配 "bizid&usercase&scenario"， 如果可以找到一个Extension, 停下来。不然就继续。
 *    3) 匹配 "bizid&usercase"，如果可以找到一个Extension, 停下来。不然就继续。
 *    4）匹配 "bizid"，如果可以找到一个Extension, 停下来。不然就继续。
 *    5）如果都无法匹配并找到一个Extension, 将抛出异常。
 * </p>
 *
 * @author  Collin Cai
 */
@Component
public class ExtensionExecutor {

    /* ---- Static ---------- */

    private static final String ERROR_MSG = "BusinessPoint can not be null for extension.";
    private static final String ERROR_MSG_1 = "Can not find extension with ExtensionPoint: %s, BizScenario: %s";
    private static final String ERROR_MSG_2 = "BusinessPointContext Cannot obtain from ContextManager";
    /* ---- Field ---------- */

    @Autowired
    private ExtensionKeeper extensionKeeper;
    private Logger logger = Logger.getLogger(ExtensionExecutor.class);
    /* ---- Methods ---------- */

    /**
     * 执行一个继承于{@link ExtensionPointI} 的具体扩展类中定义的方法。
     *
     * @param targetClz     继承与{@link ExtensionPointI}的自定义业务扩展点接口。
     * @param businessPoint 业务扩展点信息。
     * @param exeFunction   扩展点对象上定义的方法。
     * @param <R>           方法返回的类型。
     * @param <T>           方法参数类型。
     * @return 返回执行的结果。
     */
    public <R, T> R execute(Class<T> targetClz, BusinessPoint businessPoint, Function<T, R> exeFunction) {

        T component = locateComponent(targetClz, businessPoint);
        logger.info(String.format("Begin Executing Extension: %s, Method: %s", component, exeFunction));
        R result = exeFunction.apply(component);
        logger.info(String.format("End Executing Extension: %s, Method: %s", component, exeFunction));
        return result;
    }

    /**
     * 执行一个继承于{@link ExtensionPointI} 的具体扩展类中定义的方法。
     *
     * @param extensionCoordinator 指定一个{@link ExtensionCoordinator}，
     *                             其中包含{@link ExtensionPointI} 和 {@link BusinessPoint}。
     * @param exeFunction          扩展点对象上定义的方法。
     * @param <R>                  方法返回的类型。
     * @param <T>                  方法参数类型。
     * @return 返回执行的结果。
     */
    public <R, T> R execute(ExtensionCoordinator extensionCoordinator, Function<T, R> exeFunction) {
        @SuppressWarnings("unchecked")
        Class<T> targetClz = (Class<T>) extensionCoordinator.getExtensionPointClass();
        BusinessPoint point = extensionCoordinator.getBusinessPoint();
        return execute(targetClz, point, exeFunction);
    }

    /**
     * Execute extension without returning.
     *
     * @param targetClz   目标类型。
     * @param context     一个{@link BusinessPoint}对象
     * @param exeFunction 需要执行的目标对象上的方法。
     * @param <T>         Parameter Type
     */
    public <T> void executeVoid(Class<T> targetClz, BusinessPoint context, Consumer<T> exeFunction) {
        T component = locateComponent(targetClz, context);
        logger.info(String.format("Begin Executing Extension: %s, Method: %s", component, exeFunction));
        exeFunction.accept(component);
        logger.info(String.format("End Executing Extension: %s, Method: %s", component, exeFunction));
    }

    /**
     * Execute extension without returning.
     *
     * @param extensionCoordinator 一个扩展协助者对象 {@link ExtensionCoordinator}。
     * @param exeFunction          需要执行的目标对象上的方法。
     * @param <T>                  Parameter Type
     */
    public <T> void executeVoid(ExtensionCoordinator extensionCoordinator, Consumer<T> exeFunction) {
        @SuppressWarnings("unchecked")
        Class<T> targetClz = (Class<T>) extensionCoordinator.getExtensionPointClass();
        BusinessPoint point = extensionCoordinator.getBusinessPoint();
        executeVoid(targetClz, point, exeFunction);
    }

    /**
     *
     * @param targetClz
     * @param exeFunction
     * @param <R>
     * @param <T>
     * @return
     */
    public <R, T> R execute(Class<T> targetClz, Function<T, R> exeFunction) {
        BusinessPoint businessPoint = getBusinessPoint();
        return execute(targetClz, businessPoint, exeFunction);
    }

    /**
     * Execute extension without returning.
     * @param targetClz
     * @param exeFunction
     * @param <T>
     */
    public <T> void executeVoid(Class<T> targetClz, Consumer<T> exeFunction) {
        BusinessPoint businessPoint = getBusinessPoint();
        executeVoid(targetClz, businessPoint, exeFunction);
    }

    private <C> C locateComponent(Class<C> targetClz, BusinessPoint businessPoint) {
        return locateExtension(targetClz, businessPoint);
    }
    /**
     * 定位一个扩展
     *
     * <p>
     * 定位规则：
     * 假设
     * targetClz 为 CustomerValidExtensionPt Interface.
     * bizScenarioUniqueIdentity 为 "bizid&usercase&scenario"
     * 1) 那么搜索先通过CustomerValidExtensionPt获取所有实现这个接口的bean.
     * 2) 首先匹配 "bizid&usercase&scenario"， 如果可以找到一个Extension, 停下来。不然就继续。
     * 3) 匹配 "bizid&usercase"，如果可以找到一个Extension, 停下来。不然就继续。
     * 4）匹配 "bizid"，如果可以找到一个Extension, 停下来。不然就继续。
     * 5）如果都无法匹配并找到一个Extension, 将抛出异常。
     * </p>
     *
     * @param targetClz     目标类。
     * @param businessPoint 业务扩展点信息。
     */
    private <Ext> Ext locateExtension(Class<Ext> targetClz, BusinessPoint businessPoint) {
        checkNull(businessPoint);
        String uniqueIdentity = businessPoint.getUniqueId();
        logger.info(String.format("Begin Locating Extension with uniqueIdentity: %s and point: %s ", uniqueIdentity, targetClz));
        Ext extension = search(targetClz, uniqueIdentity);
        logger.info(String.format("End Locating Extension with uniqueIdentity: %s and point: %s ", uniqueIdentity, targetClz));
        if (extension != null) {
            return extension;
        }

        String result = String.format(ERROR_MSG_1, targetClz, uniqueIdentity);
        throw new FrameworkException(result);
    }

    private <Ext> Ext search(Class<Ext> targetClz, String businessPoint) {
        ExtensionCoordinator coordinator = new ExtensionCoordinator(targetClz.getName(), businessPoint);
        Map<ExtensionCoordinator, ExtensionPointI> extensions = extensionKeeper.getExtensions();
        @SuppressWarnings("unchecked")
        Ext extension = (Ext) extensions.get(coordinator);
        if (extension == null) {
            if (businessPoint == null) {
                return null;
            }
            int lastSeparatorIndex = businessPoint.lastIndexOf(BusinessPoint.SEPARATOR);
            while (lastSeparatorIndex != -1) {
                businessPoint = businessPoint.substring(0, lastSeparatorIndex);
                coordinator.setBusinessPointUniqueIdentity(businessPoint);
                extension = (Ext) extensions.get(coordinator);
                if (extension != null) {
                    break;
                }
                lastSeparatorIndex = businessPoint.lastIndexOf(BusinessPoint.SEPARATOR);
            }
        }
        return extension;
    }

    /* Todo: Need to cache for each each matching */

    private String forwardMatching(String businessPoint, int step) {

        List<String> cache = new ArrayList<>();
        cache.add(businessPoint);
        if (step == 0) {

        } else {
            int lastSeparatorIndex = businessPoint.lastIndexOf(BusinessPoint.SEPARATOR);
            while (lastSeparatorIndex != -1) {
                businessPoint = businessPoint.substring(0, lastSeparatorIndex);
                cache.add(businessPoint);
                lastSeparatorIndex = businessPoint.lastIndexOf(BusinessPoint.SEPARATOR);
            }
        }
        return null;
    }

    private void checkNull(BusinessPoint businessPoint) {
        if (businessPoint == null) {
            throw new FrameworkException(ERROR_MSG);
        }
    }

    private BusinessPoint getBusinessPoint() {
        ContextObject context = ContextManager.getContext(BusinessPointContext.class);
        BusinessPoint businessPoint = null;
        if (context == null) {
            throw new FrameworkException(ERROR_MSG_2);
        }
        businessPoint = ((BusinessPointContext) context).getCurrent();
        checkNull(businessPoint);
        return businessPoint;
    }
}

